package sistema_de_arquivos;


import Programas.Processo;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import swap.*;

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
/**
 *
 * @author modesto
 */
public class SistemaArquivos {

    public Disco disco;
    public Diretorio diretorio_atual;
    public Diretorio diretorio_raiz;
    public Swap swap;
    public Arquivo[] arquivos_abertos;
    public int tamanho_bloco;
    public Bloco blocos[];
    public Erro erro;
    public int tamanho_bytes;
    public int bytes_utilizados;
    public HashMap arquivo_to_dir = new HashMap();

    public SistemaArquivos(Disco disco, Diretorio diretorio_raiz) {
        this.disco = disco;
        this.diretorio_raiz = diretorio_raiz;
        this.diretorio_raiz.pai = null;
        this.diretorio_atual = diretorio_raiz;
        this.tamanho_bloco = 4096;
        this.blocos = new Bloco[(disco.cilindros * disco.setores_por_cilindro * 4096) / 4096];
        this.tamanho_bytes = blocos.length * this.tamanho_bloco;
        this.arquivos_abertos = new Arquivo[blocos.length];
        this.bytes_utilizados = 0;
        this.swap = new Swap();

        for (int i = 0; i < blocos.length; i++) {
            this.blocos[i] = new Bloco(i / disco.setores_por_cilindro + 1, i % disco.setores_por_cilindro + 1, true);
        }
        
        this.blocos[0].livre = false;
    }
    
    public void swap_ps()
    {
        swap.ps();
    }

    private Diretorio getDiretorioPai(String caminho)
    {
        Diretorio pai = null;
        
        if (caminho.indexOf('/') == -1)
            pai = this.diretorio_atual;
        else if (caminho.charAt(0) != '/')
            pai = this.diretorio_atual.getDiretorioRecursivo(caminho.substring(0, caminho.lastIndexOf('/')));
        else if (caminho.lastIndexOf('/') != 0)
            pai = this.diretorio_raiz.getDiretorioRecursivo(caminho.substring(0, caminho.lastIndexOf('/')));
        else
            pai = this.diretorio_raiz;
        
        return pai;
    }
    
    /**
     * Autor: Modesto
     * Obtem um descritor de arquivo livre para
     * um arquivo que esta sendo aberto.
     * @return -> Descritor de arquivo.
     */
    private int getDescritorArquivo()
    {
        for (int i = 0; i < this.arquivos_abertos.length; i++)
        {
            if (this.arquivos_abertos[i] == null)
                return i;
        }
        
        return -1;
    }
    
    /**
     * Autor: Modesto
     * Devolve um descritor de arquivo que
     * nao esta sendo mais utilizado.
     * @param descritor 
     */
    private void liberaDescritorArquivo(int descritor)
    {
        if (descritor >= 0 && descritor < this.arquivos_abertos.length)
            this.arquivos_abertos[descritor] = null;
    }
    
    /**
     * Encontra um espaço no disco de acordo com o tamanho passado
     *
     * @param tamanho -> Tamanho necessario, em bytes
     * @return -> Indíce do primeiro bloco do espaço alocado Autor: Modesto
     */
    private int getBlocoLivre(int tamanho_bytes) {
        int inicio_bloco_livre;
        int tamanho = tamanho_bytes / this.tamanho_bloco + 1;

        /* Tamanho solicitado maior que o disponivel no sistema de arquivos. */
        if (tamanho_bytes > this.tamanho_bytes) {
            return -1;
        }

        for (int i = 0; i < this.blocos.length; i++) {
            int j;
            if (this.blocos[i].livre) {
                inicio_bloco_livre = i;

                for (j = i + 1; j < inicio_bloco_livre + tamanho; j++) {
                    if (!this.blocos[j].livre) {
                        break;
                    }
                }

                if (j - inicio_bloco_livre >= tamanho) {
                    for (int k = inicio_bloco_livre; k < j; k++) {
                        this.blocos[k].livre = false;
                    }

                    return inicio_bloco_livre;
                }
            }
        }

        return -1;
    }

    /**
     *
     * @param inicio -> Início da fatia a ser liberada
     * @param tamanho_bytes -> Quantos bytes a serem liberados a partir do
     * início
     * @return -> Objeto erro informando status de saida. Autor: Modesto
     */
    private Erro liberaBlocos(int inicio, int tamanho_bytes) {
        int tamanho = tamanho_bytes / this.tamanho_bloco + 1;

        if (this.blocos[inicio].livre) {
            return new Erro(9, "Esse bloco ja esta livre.");
        }

        for (int i = inicio; i < inicio + tamanho; i++) {
            this.blocos[i].livre = true;
        }

        return new Erro(0, "");
    }

    /**
     * 
     * @param caminho
     * @return 
     */
    public Erro unlink(String caminho) {
        Diretorio pai = new Diretorio();
        
        pai = this.getDiretorioPai(caminho);
        
        if (pai == null)
            return new Erro(6, caminho + ": Caminho nao existe");

        String nomeArquivo;
        if (pai == null) {
            return new Erro(6, "Diretório inexistente: " + caminho.substring(0, caminho.lastIndexOf('/')));
        } else {
            nomeArquivo = caminho.substring(caminho.lastIndexOf('/') + 1, caminho.length());
            if (pai.arquivoExiste(nomeArquivo)) {
                Arquivo arquivo = pai.getArquivo(nomeArquivo);
                
                if (arquivo.escrita)
                {
                    this.liberaBlocos(arquivo.bloco_inicio, arquivo.tamanho);
                    this.bytes_utilizados -= arquivo.tamanho;
                    pai.delFilhoArquivo(nomeArquivo);
                    return new Erro (0,"");
                } else
                    return new Erro(3, "Sem permissao de escrita.");
            } else if (pai.diretorioExiste(nomeArquivo)) {

                Diretorio dir = pai.getDiretorio(nomeArquivo);
                
                if (dir.escrita)
                {
                        if (dir.filhos_arquivo.size() == 0 && dir.filhos_diretorio.size() == 0) {
                            this.liberaBlocos(dir.bloco_inicio, dir.tamanho);
                            pai.delFilhoDiretorio(nomeArquivo);
                            this.bytes_utilizados -= dir.tamanho;
                            return new Erro (0,"");
                        } else {
                            return new Erro (7, "Diretorio nao vazio");
                        }
                } else
                    return new Erro(3, "Sem permissao de escrita.");
            }

        }
        return null;
    }

    /**
     * @autor Higor Freitas
     * @param caminho - Pega o caminho onde será criado o novo diretório
     * @return Erro - Retorna se houver diretório com o mesmo nome
     */
    
    public Erro mkdir(String caminho){
        
        Diretorio novoDir = new Diretorio();
        Diretorio pai = new Diretorio();
        String dir;    
      
        pai = this.getDiretorioPai(caminho);
        
        if (pai == null)
            return new Erro(6, caminho.substring(0, caminho.lastIndexOf("/")) + ": Diretorio nao existe.");
        
        dir = caminho.substring(caminho.lastIndexOf("/") +1, caminho.length());
        
        if (pai.diretorioExiste(dir) == true)
            return new Erro(5, "Diretorio já existe");
        else if (!pai.escrita)
            return new Erro(3, "Sem permissao de escrita.");
        else if (this.bytes_utilizados + 4096 > this.tamanho_bytes)
             return new Erro(9, "Sem espaco em disco.");
        else {
            pai.addFilhoDiretorio(dir, true, true, getBlocoLivre(4096));
            this.bytes_utilizados += 4096;
        }
        return new Erro(0, "");
    }

     /**
     * Autora: Jéssica Melo Simões
     * @param caminhoOrigem -> o caminho do arquivo ou diretorio que vai copiado ou movido para outro diretorio
     * @param caminhoDestino -> caminho do diretorio que receberá o arquivo ou outro diretorio 
     * @return -> Erro se já existir um arquivo com o mesmo nome no diretorio de origem, ou se o nome do arquivo estiver incorreto
     */
    public Erro move (String caminhoOrigem, String caminhoDestino){
        Diretorio paiOrigem = new Diretorio();
        Diretorio paiDestino = new Diretorio();
        String nome_temp;
        
        paiOrigem = this.getDiretorioPai(caminhoOrigem);
        paiDestino = this.getDiretorioPai(caminhoDestino);
        
        if (paiOrigem == null)
            return new Erro(6, caminhoOrigem + ": Diretorio nao existe.");
        else if (paiDestino == null)
            return new Erro(6, caminhoDestino + ": Diretorio nao existe.");
        
        nome_temp = caminhoOrigem.substring(caminhoOrigem.lastIndexOf("/")+1, caminhoOrigem.length());
                
        if(paiOrigem.arquivoExiste(nome_temp) == true){
            if (paiDestino.arquivoExiste(nome_temp) || paiDestino.diretorioExiste(nome_temp))
                return new Erro(5, "Arquivo ou diretorio ja existe no diretorio de destino.");
            else if (!paiOrigem.getArquivo(nome_temp).leitura)
                return new Erro(4, "Sem permissao de leitura no arquivo de origem.");
            else if (!paiDestino.escrita)
                return new Erro(3, "Sem permissao de escrita no diretorio de destino.");
            else {
                paiDestino.filhos_arquivo.add(paiOrigem.getArquivo(nome_temp));
                paiOrigem.filhos_arquivo.remove(paiOrigem.getArquivo(nome_temp));
            }
        } else if (paiOrigem.diretorioExiste(nome_temp)){
            if(paiDestino.diretorioExiste(nome_temp) || paiDestino.arquivoExiste(nome_temp))
                return new Erro(5, "Diretorio ou arquivo ja existe no diretorio de destino.");
            else if (paiOrigem.getDiretorio(nome_temp).filhos_arquivo.size() > 0 ||
                      paiOrigem.getDiretorio(nome_temp).filhos_diretorio.size() > 0)
                return new Erro(7, "Diretorio nao vazio.");
            else if (!paiOrigem.getDiretorio(nome_temp).leitura)
                return new Erro(4, "Sem permissao de leitura no diretorio de origem.");
            else if (!paiDestino.escrita)
                return new Erro(3, "Sem permissao de escrita no diretorio de destino.");
            else {
                paiDestino.filhos_diretorio.add(paiOrigem.getDiretorio(nome_temp));
                paiOrigem.filhos_diretorio.remove(paiOrigem.getDiretorio(nome_temp));
            }
        } else{
            return new Erro(6, "Diretorio nao existe no diretorio de origem.");   
        }
        return new Erro(0, "");
    }

    /**
     * Autor: Modesto
     * @param caminho -> Caminho para o novo diretorio de trabalho
     * @return -> Objeto Erro informando status de saida
     */
    public Erro chdir(String caminho) {
        
        Diretorio tmp;

        if (caminho.equals(".."))
        {
            if (this.diretorio_atual != this.diretorio_raiz)
                tmp = (Diretorio)this.diretorio_atual.pai;
        }
        if (caminho.charAt(0) == '/')
            tmp = this.diretorio_raiz.getDiretorioRecursivo(caminho);
        else
            tmp = this.diretorio_atual.getDiretorioRecursivo(caminho);
 
                
        if (!tmp.leitura)
            return new Erro(4, tmp.nome + ": Sem permissao de leitura.");
        
        if (tmp != null) {
            this.diretorio_atual = tmp;
            return new Erro(0, "");
        } else {
            return new Erro(6, "Diretorio inexistente.");
        }
    }

    /**
     * Autor: Modesto
     * Abre um arquivo que esta em disco
     * @param caminho -> Caminho para o arquivo.
     * @param executavel -> Executavel ou nao
     * @param escrita -> Permissao de escrita
     * @param leitura -> Permissao de leitura
     * @return  -> Descritor de arquivo do arquivo aberto.
     */
    public Integer open(String caminho, boolean executavel, boolean escrita, boolean leitura) {
        Diretorio pai;
        String nome_arquivo;
        Arquivo arquivo;
        Erro erro;
        int descritor;
        
        int tamanho = (int) (Math.random() * (this.tamanho_bytes / 2)); // Tamanho aleatorio, sendo no maximo a metade da capacidade do s.a

        if (tamanho + this.bytes_utilizados > this.tamanho_bytes)
        {
            System.out.println("open: Sem espaco em disco.");
            return -1;
        }
        
        pai = this.getDiretorioPai(caminho);
        
        if (!pai.escrita)
        {
            System.out.println("open(): Sem permissao de escrita.");
            return -1;
        }
        
        nome_arquivo = caminho.substring(caminho.lastIndexOf('/') + 1, caminho.length()); // remove caminho

        arquivo = pai.getArquivo(nome_arquivo);

        if (arquivo == null) {
            int bloco_inicio = this.getBlocoLivre(tamanho);

            if (bloco_inicio != -1) {
                erro = pai.addFilhoArquivo(nome_arquivo, executavel, escrita, leitura, tamanho, bloco_inicio);
                descritor = this.getDescritorArquivo();
                if (descritor == -1)
                    return -1;
                this.arquivos_abertos[descritor] = pai.getArquivo(nome_arquivo);
                this.bytes_utilizados += tamanho;
                this.arquivo_to_dir.put(nome_arquivo, pai.pwd()); // Adiciona diretorio no hashmap
            } else {
                return -1;
            }
        } else {
            descritor = this.getDescritorArquivo();
            this.arquivos_abertos[descritor] = arquivo;
        }

        return descritor;
    }
    
    public Erro close(int descritor)
    {
        if (descritor < 0 || descritor >= this.arquivos_abertos.length || this.arquivos_abertos[descritor] == null)
            return new Erro(10, "Descritor de arquivo invalido.");
        else {
            this.liberaDescritorArquivo(descritor);
            return new Erro(0, "");
        }
    }

    /**
     * Autora: Dalvana Lopes Função para retorno de status de um determinado
     * arquivo ou diretorio.
     *
     * @param caminho -> Caminho para se buscar o caminho ou diretorio.
     * @return -> Retorna o objeto StatusArquivo com os dados disponiveis
     *
     */
    public StatusArquivo stat(String caminho) {
        StatusArquivo status_arquivo = new StatusArquivo();
        String nome_arquivo;
        Diretorio diretorio_pai;
        Diretorio dir;
        Arquivo arq;

        
        if (caminho.indexOf('/') == -1)
            nome_arquivo = caminho;
        else
            nome_arquivo = caminho.substring(caminho.lastIndexOf('/') + 1, caminho.length());
     
        diretorio_pai = this.getDiretorioPai(caminho);
                
        if (diretorio_pai == null)
            return null;

        if ((dir = diretorio_pai.getDiretorioRecursivo(nome_arquivo)) != null) {
            status_arquivo.data_criacao = dir.data_criacao;
            status_arquivo.escrita = dir.escrita;
            status_arquivo.executavel = dir.executavel;
            status_arquivo.leitura = dir.leitura;
            status_arquivo.tamanho = dir.tamanho;
            status_arquivo.nome = dir.nome;

        } else if ((arq = diretorio_pai.getArquivo(nome_arquivo)) != null) {
            status_arquivo.data_criacao = arq.data_criacao;
            status_arquivo.escrita = arq.escrita;
            status_arquivo.executavel = arq.executavel;
            status_arquivo.leitura = arq.leitura;
            status_arquivo.tamanho = arq.tamanho;
            status_arquivo.nome = arq.nome;

        } else {
            return null;
        }



        return status_arquivo;

    }

    /**
     * Autor: Jessica
     *
     * @param origem -> Arquivo a ser copiado
     * @param destino -> Diretorio de destino do arquivo
     * @return -> Objeto Erro com status de saida
     */
    //Erro move(String origem, String destino)
    public String pwd() {
        /*
        ArrayList <String> dir_tree = new ArrayList<String>();
        String caminho = "/";
        
        for (Diretorio cur_dir = this.diretorio_atual; cur_dir.pai != null; cur_dir = (Diretorio)cur_dir.pai)
        {
            dir_tree.add(cur_dir.nome);
        }
        
        for (int i = dir_tree.size() - 1; i >= 0; i--)
        {
            caminho = caminho + dir_tree.get(i);
            
            caminho += (i == 0) ? "" : "/";
        }
        
        return caminho;
        */
       
        return this.diretorio_atual.pwd();
    }
    

    /**
     * Lista arquivos e diretorios
     * @param comportamento -> Altera opcoes de listagem
     *  l: Lista detalhes do arquivo
     *  d: Lista diretorios
     *  f: Lista arquivos
     */
    public ArrayList<String> ls(String comportamento)
    {
        ArrayList<String> retorno = new ArrayList<String>();
        
        boolean listar_detalhes, listar_arquivos, listar_diretorios;
        int index = 0;
        listar_detalhes = comportamento.indexOf('l') != -1;
        listar_arquivos = comportamento.indexOf('f') != -1;
        listar_diretorios = comportamento.indexOf('d') != -1;

        
        // Listagem padrao
        if (!listar_arquivos && !listar_diretorios)
        {
            listar_arquivos = true;
            listar_diretorios = true;
        }
        
        if (listar_diretorios)
        {
            for (Diretorio dir : this.diretorio_atual.filhos_diretorio)
            {
                String str;
                
                str = "";
                
                str += ("d: " + index++ + "\t" + dir.nome);
                
                if (listar_detalhes)
                {
                    str += ("\t" + dir.data_criacao + "\t" + dir.tamanho);
                    str +=("\t" + dir.bloco_inicio + "\t" + dir.leitura + "\t" + dir.escrita);
                }
                
                str += "\n";
                
                retorno.add(str);
             }
        }
        
        if (listar_arquivos)
        {
            for (Arquivo arq : this.diretorio_atual.filhos_arquivo)
            {
                String str;
                
                str = "";
                
                str += ("a: " + index++ + "\t" + arq.nome);
                
                if (listar_detalhes)
                {
                    str += ("\t" + arq.data_criacao + "\t" + arq.tamanho);
                    str += ("\t" + arq.bloco_inicio + "\t" + arq.leitura + "\t" + arq.escrita + "\t" + arq.executavel);
                }
                
                str += "\n";
                
                retorno.add(str);
            }            
        }
        
        return retorno;
    }
    
    public String df()
    {
        String str = "";
        
        str += "Tamanho (Bytes): " + this.tamanho_bytes + "\n";
        str += "Espaco utilizado(Bytes): " + this.bytes_utilizados + "\n";
        str += "Numero de cilindros: " + this.disco.cilindros + "\n";
        str += "Numero de setores por cilindro: " + this.disco.setores_por_cilindro + "\n";
        str += "Numero de blocos: " + this.blocos.length + "\n";
        str += "Tamanho do bloco: " + this.tamanho_bloco + "\n";
        
        return str;
    }

    public HashMap lsof()
    {
        HashMap hm = new HashMap();
        
        for (int i = 0; i < this.arquivos_abertos.length; i++)
        {
            if (this.arquivos_abertos[i] != null)
            {
                hm.put(i, this.arquivos_abertos[i].nome);
            }
        }
        
        return hm;
    }

    /**
     * Adiciona processo a swap
     * @param pid -> ID do Processo
     * @param processo -> Processo a ser adicionado
     */
    public void swap_put(int pid, Processo processo)
    {
        ProcessoEmSwap processo_swap;
        int bloco_inicio = this.getBlocoLivre(processo.getTamanho());
        
        if (bloco_inicio == -1)
            return;
        
        processo_swap = new ProcessoEmSwap(processo, bloco_inicio);
        
        swap.putProcesso(pid, processo_swap);
    }
    
   
   /************** MAPEAMENTO DE FUNCOES DO SHELL ***************/
    
    public void shell_ls(String comportamento)
    {
        if (comportamento.equalsIgnoreCase("--help"))
        {
            System.out.println("ls - help");
            System.out.println("\t Sintaxe: ls [<opcoes>]");
            System.out.println("\t\t Opcoes:");
            System.out.println("\t\t\tPadrao: df");
            System.out.println("\t\t\tl -> Lista detalhes do arquivo: tamanho, data de criacao, etc.");
            System.out.println("\t\t\td -> Lista diretorios");
            System.out.println("\t\t\tf -> Lista arquivos");
            
        }
        
        for (String str : this.ls(comportamento))
        {
            System.out.print(str);
        }
    }
    
    public void shell_mkdir(String caminho)
    {        
        Erro erro = this.mkdir(caminho);
        
        if (erro.codigo != 0)
            System.out.println("Erro " + erro.codigo + ": " + erro.mensagem);
        else
            System.out.println("mkdir: +OK");
    }
    
    public void shell_df()
    {
        System.out.println(this.df());
        System.out.println("df: +OK");
    }
    
    public void shell_mdir(String caminho)
    {
        int fd = this.open(caminho, true, true, true);
        
        if (fd == -1)
        {
            System.out.println("mdir: erro, verifique os parametros.");
        } else
            System.out.println("Arquivo aberto, descritor: " + fd);

    }
    
    public void shell_pwd()
    {
        System.out.println(this.pwd());
    }
    
    public void shell_rm(String caminho)
    {
        Erro erro = this.unlink(caminho);
        
        if (erro.codigo != 0)
            System.out.println("Erro " + erro.codigo + ": " + erro.mensagem);
        else
            System.out.println("rm: +OK");
    }
    
    public void shell_cd(String caminho)
    {
        Erro erro = this.chdir(caminho);
        
        if (erro.codigo != 0)
            System.out.println("Erro " + erro.codigo + ": " + erro.mensagem);
        else
            System.out.println("cd: +OK");
    }
    
    public void shell_lsof()
    {
        Iterator it = this.lsof().entrySet().iterator();
        
        System.out.println("D.A\tNome");
        
        while (it.hasNext())
        {
            Map.Entry pairs = (Map.Entry)it.next();
            System.out.println(pairs.getKey() + "\t" + pairs.getValue());        
        }
        
        System.out.println();
        
        System.out.println("lsof: +OK");
    }
    
    public void shell_close(int fd)
    {
        Erro erro = this.close(fd);
        
        if (erro.codigo != 0)
            System.out.println("Erro " + erro.codigo + ": " + erro.mensagem);
        else
            System.out.println("close: +OK");
    }
    
    public void shell_stat(String caminho)
    {
        StatusArquivo sa = this.stat(caminho);
        
        if (sa == null)
            System.out.println("sa: Erro, confira se o arquivo existe.");
        else {
            System.out.println("Stat: " + caminho);
            System.out.println("Nome: " + sa.nome);
            System.out.println("Tamanho: " + sa.tamanho);
            System.out.println("Data criacao: " + sa.data_criacao);
            System.out.println("Leitura: " +  sa.leitura);
            System.out.println("Escrita: " + sa.escrita);
            System.out.println("Execucao: " + sa.executavel);
            System.out.println("stat: +OK");
        }
    }
    
    public void shell_mv(String caminho_origem, String caminho_destino)
    {
        Erro erro = this.move(caminho_origem, caminho_destino);
        
        if (erro.codigo != 0)
            System.out.println("Erro " + erro.codigo + ": " + erro.mensagem);
        else
            System.out.println("mv: +OK");
    }
    
    /**************************** SWAP *****************************/
    /**
     * Obtem processo da swap
     * @param pid -> ID do processo
     * @return 
     */
    public Processo swap_get(int pid)
    {
        ProcessoEmSwap processo_swap = swap.getProcesso(pid);
        Processo processo;
        
        if (processo_swap == null)
            return null;
        else {
            processo = processo_swap.processo;
            this.liberaBlocos(processo_swap.bloco_inicio, processo.getTamanho());
            return processo;
        }
    }
    public Processo swap_get_by_gid(int gid)
    {
        ProcessoEmSwap p = swap.getProcessoByGrupo(gid);
        
        if (p == null)
            return null;
        else {
             this.liberaBlocos(p.bloco_inicio, p.processo.getTamanho());
         return p.processo;
        }             
    }
}
